const path = require('path');
const fs = require('fs');
const os = require('os');
const http = require('http');
const https = require("https");
var url = require('url');

const fileSplitter = path.sep;

const isWin = os.platform().startsWith('win');

/**
 * windows下最前面会出现/,如果不处理,node文件操作会以为是相对路径
 * @param {*} p 
 * @returns 
 */
function nomalizePath(p) {
    if (p && isWin && p.startsWith('/')) return p.substring(1);
    return p;
}

/**
 * 在创建文件之前,确保文件夹被创建了
 * @param {String} filePath 
 */
function confirmDir(filePath) {
    const dirPath = path.dirname(filePath);
    if (!fs.existsSync(dirPath)) {
        const ss = dirPath.split(fileSplitter);
        let dir = ss.shift();
        ss.forEach(s => {
            dir += fileSplitter + s;
            !fs.existsSync(dir) && fs.mkdirSync(dir);
        });
    }
}

function undef(v) {
    return typeof v === 'undefined' || v === null;
}

/**
 * 从web端移植过来
 */
class FrameUrl {
    /**
     * @param {String} url 
     */
    constructor(url) {
        this.params = {};
        const questionIndex = url.indexOf('?');
        this.prefix = url.substring(0, questionIndex);
        url.substring(questionIndex + 1).split('&').forEach(seg => {
            const kv = seg.split("=");
            this.params[kv[0]] = kv[1];
        });
        this.url = url;
    }

    getIpPort() {
        const p = this.prefix;
        let i = p.indexOf('//');
        i < 0 && (i = 0);
        return p.substring(i, p.indexOf('/', i + 2));
    }

    getParam(name) {
        return this.params[name];
    }

    setParam(name, value) {
        if (typeof name === 'object') {
            for (let n in name) {
                this.setParam(n, name[n]);
            }
        } else {
            this.params[name] = value;
        }
        return this;
    }

    toString() {
        let re = this.prefix + '?', i = 0;
        for (let n in this.params) {
            const v = this.params[n];
            re += (i++ > 0 ? '&' : '') + n + '=' + v;
        }

        return re;
    }
}



function _debug(cb) {
    const orig = Error.prepareStackTrace;
    Error.prepareStackTrace = function (_, stack) { return stack; };
    const err = new Error;
    Error.captureStackTrace(err, arguments.callee);
    const stack = err.stack;
    Error.prepareStackTrace = orig;
    //取索引位置为1的栈
    const stack1 = stack[2];
    // @ts-ignore
    const _line = stack1.getLineNumber();
    // @ts-ignore
    const _file = stack1.getFileName();
    cb(_file, _line);
}

/**
 * from Meizz of csdn
 * @param {Date} date
 * @param {String} fmt
 * @return {String}
 */
function formatDate(date, fmt) {
    var o = {
        "M+": date.getMonth() + 1,               //月份   
        "d+": date.getDate(),                    //日   
        "h+": date.getHours(),                   //小时   
        "m+": date.getMinutes(),                 //分   
        "s+": date.getSeconds(),                 //秒   
        "q+": Math.floor((date.getMonth() + 3) / 3), //季度   
        "S": date.getMilliseconds()             //毫秒   
    };
    if (/(y+)/.test(fmt))
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o)
        if (new RegExp("(" + k + ")").test(fmt))
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    return fmt;
}

function getNowString() {
    return formatDate(new Date(), "yy-MM-dd hh:mm:ss S");
}

function _info(func, args) {
    _debug((_file, _line) => {
        let t, f;
        //彩色模式下打印对象会出乱码
        if (args.find(a => typeof a === 'object')) {
            t = getNowString();
            f = _file + ':' + _line;
        } else {
            t = "\u001b[32m" + getNowString();
            f = "\u001b[34m" + _file + ':' + _line + "\u001b[0m";
        }

        func(t, f, ...args);
    })
}

/**
 * 打印调试信息
 */
function info(...args) {
    _info(console.info, args);
}

/**
 * 打印错误调试信息
 */
function errorInfo(...args) {
    _info(console.error, args);
}



/**
 * 遍历文件夹
 * @param {string} filePath 
 * @param {function(String):any} callback 
 */
function travelDir(filePath, callback) {
    let state = fs.statSync(filePath);
    if (state.isFile()) {
        callback(filePath)
    } else if (state.isDirectory()) {
        fs.readdirSync(filePath).forEach(file => {
            const subFilePath = path.join(filePath, file);
            //先序
            travelDir(subFilePath, callback);
        });
    }
}





/**
 * 不想引入jquery,直接写一个defer,实现内转外的控制
 */
class Defer {
    constructor(promise = null) {
        if (promise) {
            this.promise = promise;
        } else {
            this.promise = new Promise((resolve, reject) => {
                this._resolve = resolve;
                this._reject = reject;
            });
        }
    }

    then(cb) {
        return this.promise.then(cb);
    }

    util(overtime) {
        return new Promise((resolve, reject) => {
            this.promise.then(function () {
                // @ts-ignore
                resolve(...arguments);
            }, function () {
                reject(...arguments);
            });
            setTimeout(() => {
                reject(new Error(`overtime:${overtime}`))
            }, overtime)
        });
    }

    catch(cb) {
        return this.promise.catch(cb);
    }

    resolve(res) {
        this._resolve.apply(null, [...arguments]);
    }

    reject(e) {
        this._reject.apply(null, [...arguments]);
    }

    static reslovedDefer() {
        const defer = new Defer();
        defer.resolve(...arguments)
        return defer;
    }
}

async function asyncSleep(millionSeconds = 1000) {
    const defer = new Defer();
    setTimeout(() => { defer.resolve(); }, millionSeconds);
    return defer.promise;
}

/**
 * 注意这个类似于golang的方法调用,返回Promise<[success,fail]>
 * @param {function} call 
 */
async function promisify(call) {
    const defer = new Defer();
    //只返回一次
    function resolve(res, err) {
        defer.resolve([...arguments]);
    }
    try {
        call(resolve);
    } catch (e) {
        defer.resolve([, e]);
    }

    return defer.promise;
}

/**
 * 遍历文件夹,并回调文件路径,忽略文件夹
 * @param {String} filePath 
 * @param {function(String)} callback 
 * @returns 
 */
async function travelDirAsync(filePath, callback) {
    const [stats, err] = await promisify(resolve => {
        fs.stat(filePath, (err, stats) => {
            resolve(stats, err);
        })
    });
    if (err) throw err;
    if (stats.isFile()) {
        return callback(filePath);
    } else if (stats.isDirectory()) {
        const [files, err] = await promisify(resolve => {
            fs.readdir(filePath, (err, files) => {
                resolve(files, err);
            })
        });
        if (err) throw err;
        if (files && files.length) {
            for (let i = 0, l = files.length; i < l; i++) {
                const file = files[i];
                const subFilePath = path.join(filePath, file);
                if (await travelDirAsync(subFilePath, callback) === false) {
                    return false;
                }
            }
        }
    }
}

/**
 * 异步任务异步执行队列,这个实现确实更加直观,逻辑更加顺畅
 * 异步任务与同步任务
 * 异步执行与同步执行
 * 异步任务通过await写成同步写法
 * 异步执行通过await写成同步写法
 */
class AsyncTaskQueue {
    /**
     * @type {Function}
     */
    static warn;

    queue = [];

    add(task) {
        if (this.queue.length < this.maxQueueLength) {
            this.queue.push(task);
            //notify await
            this.waitDefer.resolve(task);
            return true;
        } else {
            //AsyncTaskQueue.warn("过多或过快的请求!,已经取消");
            return false;
        }
    }

    size() {
        return this.queue.length;
    }

    /**
     * 是否在执行或是任务队列不空(在执行中间的延迟休息状态)
     */
    running = true;
    /**
     * 最大队列长度
     * 限制用户频率过快的请求
     */
    maxQueueLength = 2;
    /**
     * 两次执行的间隔
     */
    intervalBetweenTasks = 300;

    /**
     * 从用户请求到执行的最小延迟
     */
    requestDelay = 15;
    /**
     * 错误之后,重试或下一个任务的延迟执行时间
     * 注意:延迟也是错误的一种,也会重试,而在某些业务下这样的逻辑可能有问题
     */
    waitAfterError = 3000;
    /**
     * 每个异步任务的超时时间,超过时间就reject(超时异常)
     */
    defaultOverTime = 3500;
    /**
     * 每个任务的最多执行的次数(包括第一次执行和报错后的重试)
     */
    maxCallTimes = 2;

    constructor() {
        this.waitDefer = new Defer();
        //其实能访问到this,因为ide报了提示
        const me = this;
        /**
         * generator
         */
        this[Symbol.asyncIterator] = async function* () {
            while (true) {
                if (me.queue.length) {
                    me.running = true;
                    const defer = new Defer(), task = me.queue.shift();
                    try {
                        /**
                         * 在generate时执行task
                         */
                        //info('call:',task.methodName);
                        const p = task.call();
                        if (p && p instanceof Promise) {
                            const overTime = task['overtime'] || me.defaultOverTime;
                            /**
                             * 在generate时执行task
                             */
                            new Defer(p).util(overTime).then(
                                () => {
                                    //成功时,resolve undefined
                                    defer.resolve();
                                },
                                (e) => {
                                    errorInfo(e);
                                    defer.resolve(me.handleTaskError(task, e));
                                }
                            );
                        } else {
                            //兼容同步任务
                            defer.resolve();
                        }
                    } catch (e) {
                        errorInfo(e);
                        defer.resolve(me.handleTaskError(task, e));
                    }

                    yield defer.promise;
                } else {
                    me.running = false;
                    //队列为空时,刷新waitDefer
                    this.waitDefer = new Defer();
                    yield me.awaitForAdd();
                }
            }
        }
    }

    /**
     * 错误处理
     */
    handleTaskError(task, e) {
        let callTimes = task.callTimes || 0;
        //重试次数阈值
        if (++callTimes < this.maxCallTimes) {
            task.callTimes = callTimes;
            /**
             * 将出错的任务重新放入队列头部
             */
            this.queue.unshift(task);
            this.queue.length = this.maxQueueLength;
        }
        //异常信息包装为Error
        !(e instanceof Error) && (e = new Error(e + ''));

        AsyncTaskQueue.warn(e.message);

        return e;
    }

    awaitForAdd() {
        return this.waitDefer.promise;
    }

    async launch() {
        for await (let res of this) {
            /**
             * 两种不同的延迟时间
             */
            if (res instanceof Error) {
                await asyncSleep(this.waitAfterError);
            } else if (typeof res === 'function') {
                /**
                * 如果res是方法,也就是notify
                */
                await asyncSleep(this.requestDelay);
            } else {
                await asyncSleep(this.intervalBetweenTasks);
            }

        }
    }//~
}

class EventSource {
    /**
     * eventName=>[listener]
     */
    _listeners = {};

    addListener(eventName, listener) {
        let ls = this._listeners[eventName];
        !ls && (ls = this._listeners[eventName] = []);

        ls.push(listener);
    }

    fireListener(eventName) {
        const ls = this._listeners[eventName];
        if (ls && ls.length) {
            const args = [...arguments];
            args.shift();
            //遍历监听器
            ls.forEach(l => {
                try {
                    //调用监听器
                    l.apply(this, args);
                } catch (e) {
                    console.error(e);
                }
            });
        }
    }
}

class ServiceHandler {
    context = {};
    constructor() {
        this.services = {};
        this.requestQueue = new AsyncTaskQueue();
        this.requestQueue.launch();
    }

    /**
     * 同步的任务不做并发控制
     * @param {String|Object} funcName 
     * @param func 
     */
    registerService(funcName, func) {
        if (typeof funcName === 'object') {
            for (let fName in funcName) {
                this.registerService(fName, function () {
                    funcName[fName].apply(funcName, [...arguments]);
                });
            }
            return;
        }

        this.services[funcName] = func;
    }

    /**
     * 使用工作队列,使得async方法可以一个个按照添加到队列中顺序执行(同时只能有一个异步任务执行),使得方法中的await也可以起作用
     * @param {String|Object} funcName 
     * @param func 
     */
    regsiterAsyncService(funcName, func) {
        if (typeof funcName === 'object') {
            for (let fName in funcName) {
                this.regsiterAsyncService(fName, function () {
                    funcName[fName].apply(funcName, [...arguments]);
                });
            }
            return;
        }

        const services = this.services;
        const requestQueue = this.requestQueue;

        //将(可能是异步的)方法的执行改成异步执行
        const funcWrapper = services[funcName] = function () {
            const action = () => {
                func.call(services, ...arguments);
            }
            action.methodName = funcName;
            requestQueue.add(action);
        }

        funcWrapper['__asyncWrapper'] = true;
    }

    serviceNames() {
        const res = { names: [], wrapperNames: [] };
        for (let n in this.services) {
            const f = this.services[n];
            if (typeof f === 'function') {
                if (f['__asyncWrapper']) {
                    res.wrapperNames.push(n);
                } else {
                    res.names.push(n);
                }
            }
        }

        return res;
    }

}

var querystring = require('querystring');

/**
 * 支持https,支持重定向
 * @param {String} targetUrl 
 * @param {{}} postParams 
 * @return {Promise<Buffer>}
 */
async function getHttp(targetUrl, postParams = null) {
    let options, content;
    const { hostname, port, pathname } = url.parse(targetUrl);
    if (postParams) {
        content = querystring.stringify(postParams);
        options = {
            hostname, port, pathname,
            method: 'post',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': content.length
            }
        }
    }

    return new Promise((resolve, reject) => {
        const isHttps = targetUrl.startsWith('https://');
        const req = (isHttps ? https : http).request(options || targetUrl, async res => {
            if (res.statusCode !== 200) {
                //简单处理重定向
                if (res.statusCode === 302) {
                    const location = res.headers.location;
                    if (location) {
                        /**
                         * 重新生成targetUrl并递归请求
                         */
                        targetUrl = (isHttps ? 'https' : 'http') + '://' + hostname + location;
                        try {
                            const c = await getHttp(targetUrl, postParams);
                            resolve(c);
                        } catch (e) {
                            reject(e);
                        }
                        return;
                    }
                }
                //可能返回304
                reject(new Error(`request to ${targetUrl} return ${res.statusCode}!`));
            } else {
                /**
                 * @type {Buffer}
                 */
                let buf;
                res.on('data', b => {
                    buf = buf ? Buffer.concat([buf, b]) : b;
                }).on('end', _ => {
                    /**
                     * 这里没有转成字符串,因为有可能是图片等二进制文件
                     */
                    resolve(buf);
                }).on('error', e => {
                    reject(e);
                });
            }
        });

        content && req.write(content, 'utf-8');
        req.end();
    });
}

const net = require('net');
/**
 *  查看端口是否被占用
 *  注意在连接异常的时候会向外抛出异常,在调用时应该使用try catch
 */
 async function checkPortUsed(port, host = '127.0.0.1') {
    return new Promise((resolve, reject) => {
        const socket = new net.Socket();
        let inUse = true;

        function cleanUp() {
            if (socket) {
                socket.removeAllListeners('connect');
                socket.removeAllListeners('error');
                socket.end();
                socket.destroy();
                socket.unref();
            }
        }

        function onConnectCb() {
            resolve(inUse);
            cleanUp();
        }

        function onErrorCb(err) {
            if (err.code !== 'ECONNREFUSED') {
                reject(err);
            } else {
                inUse = false;
                resolve(inUse);
            }
            cleanUp();
        }

        socket.once('connect', onConnectCb);
        socket.once('error', onErrorCb);
        socket.connect({port: port, host}, function () {
        });
    });
}


module.exports = { isWin,nomalizePath, fileSplitter, Defer, confirmDir, FrameUrl, formatDate, undef, info, errorInfo, travelDir, travelDirAsync, promisify, AsyncTaskQueue, asyncSleep, EventSource, ServiceHandler, getHttp,checkPortUsed };